{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "4bb28db7",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import torch\n",
    "import torch.optim as optim\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34caafdf",
   "metadata": {},
   "outputs": [],
   "source": [
    "features = pd.read_csv('temps.csv')\n",
    "features.head()\n",
    "# 数据说明：\n",
    "# year\n",
    "# month\n",
    "# day\n",
    "# week\n",
    "# temp_2 前天最高气温\n",
    "# temp_1 昨天最高气温\n",
    "# average 历史上这天的平均气温\n",
    "# actual 实际值（Y值）\n",
    "# friend"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e3841c48",
   "metadata": {},
   "outputs": [],
   "source": [
    "print('数据维度：', features.shape)\n",
    "# 数据维度：(348, 9)\n",
    "# 348条数据，9个特征"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3c0fa42d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 处理时间数据\n",
    "import datetime\n",
    "\n",
    "# 分别得到年，月，日\n",
    "years = features['year']\n",
    "months = features['month']\n",
    "days = features['day']\n",
    "\n",
    "# datetime格式\n",
    "dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip (years, months, days)]\n",
    "dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]\n",
    "\n",
    "# 显示前5行\n",
    "dates[:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4dee6427",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 准备画图\n",
    "# 指定默认风格\n",
    "plt.style.use('fivethirtyeight')\n",
    "\n",
    "# 设置布局\n",
    "fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, figsize=(10, 10))\n",
    "fig.autofmt.xdate(rotation=45)\n",
    "\n",
    "#标签值\n",
    "ax1.plot(dates, features['actual'])\n",
    "ax1.set_xlabel(''); ax1.set_ylabel('Temperature'); ax1.set_title('Max Temp')\n",
    "\n",
    "# 昨天\n",
    "ax2.plot(dates, features['temp_1'])\n",
    "ax2.set_xlabel(''); ax2.set_ylabel('Temperature'); ax2.set_title('Previous Max Temp')\n",
    "\n",
    "# 前天\n",
    "ax3.plot(dates, features['temp_2'])\n",
    "ax3.set_xlabel('Date'); ax3.set_ylabel('Temperature'); ax3.set_title('Tow Days Prior Max Temp')\n",
    "\n",
    "# 我的逗逼朋友\n",
    "ax4.plot(dates, features['friend'])\n",
    "ax4.set_xlabel('Date'); ax4.set_ylabel('Temperature'); ax4.set_title('Friend Estimate')\n",
    "\n",
    "plt.tight_layout(pad=2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "646405e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 星期独热编码处理\n",
    "features = pd.get_dummies(features)\n",
    "features.head(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24636781",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 指定标签（Y值）\n",
    "labels = np.array(features['actual'])\n",
    "\n",
    "# 在特征中去掉标签（去掉Y值，剩下x值）\n",
    "features = features.drop('actual', axis=1)\n",
    "\n",
    "# 名字单独保存一下，以备后患\n",
    "feature_list = list(features.columns)\n",
    "\n",
    "# 转换成合适的格式\n",
    "features = np.array(features)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b889e174",
   "metadata": {},
   "outputs": [],
   "source": [
    "features.shape\n",
    "# (348, 14)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e076e017",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 数据预处理\n",
    "from sklearn import preprocessing\n",
    "\n",
    "# 标准化操作\n",
    "input_features =  preprocessing.StandardScaler().fit_transform(features)\n",
    "\n",
    "# 标准化后，数值浮动范围更新，更收敛\n",
    "# 查看\n",
    "input_features[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5be3cae8",
   "metadata": {},
   "source": [
    "#### 构建网络模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a9882346",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 将input_features 转成tensor格式\n",
    "x = torch.tensor(input_features, dtype=float)\n",
    "y = torch.tensor(labels, dtype=float)\n",
    "\n",
    "# 权重参数初始化\n",
    "weights = torch.randn((14, 128), dtype=float, requires_grad=True)\n",
    "biases = torch.randn(128, dtype=float, requires_grad=True)\n",
    "weights2 = torch.randn((128, 1), dtype=float, requires_grad=True)\n",
    "biases2 = torch.randn(1, dtype=float, requires_grad=True)\n",
    "\n",
    "learning_rate = 0.001\n",
    "losses = []\n",
    "\n",
    "for i in range(1000):\n",
    "    # 计算隐层（x.mm(weights) -> 矩阵乘法）\n",
    "    hidden = x.mm(weights) + biases\n",
    "    # 加入激活函数\n",
    "    hidden = torch.relu(hidden)\n",
    "    # 预测结果\n",
    "    predictions = hidden.mm(weights2) + biases2\n",
    "    ################## 前向传播结束 ##################\n",
    "    \n",
    "    # 通计算损失（预测值 - 真实值 => 均方误差）\n",
    "    loss = torch.mean((predictions - y) ** 2)\n",
    "    losses.append(loss.data.numpy())\n",
    "    \n",
    "    # 打印损失值\n",
    "    if i % 100 == 0:\n",
    "        print('loss: ', loss)\n",
    "    \n",
    "    # 反向传播计算（如何基于损失来更新w1 b1, w2 b2 ...）\n",
    "    # 得到weights, biases, weights2, biases2 的梯度\n",
    "    loss.backward()\n",
    "    \n",
    "    # 更新参数\n",
    "    weights.data.add_(- learning_rate * weights.grad.data)\n",
    "    biases.data.add_(- learning_rate * biases.grad.data)\n",
    "    weights2.data.add_(- learning_rate * weights2.grad.data)\n",
    "    biases2.data.add_(- learning_rate * biases2.grad.data)\n",
    "    \n",
    "    # 每次迭代都得记得清空（梯度清空）\n",
    "    weights.grad.data.zero_()\n",
    "    biases.grad.data.zero_()\n",
    "    weights2.grad.data.zero_()\n",
    "    biases2.grad.data.zero_()\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "523db5d3",
   "metadata": {},
   "source": [
    "#### 更简单的方法构建网络模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e88326e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "input_size = input_features.shape[1]\n",
    "hidden_size = 128\n",
    "output_size = 1\n",
    "batch_size = 16\n",
    "my_nn = torch.nn.Sequential(\n",
    "    torch.nn.Linear(input_size, hidden_size),\n",
    "    torch.nn.Sigmoid(),\n",
    "    torch.nn.Linear(hidden_size, output_size)\n",
    ")\n",
    "cost = torch.nn.MSELoss(reduction='mean')\n",
    "optimizer = torch.optim.Adam(my_nn.parameters(), lr=0.001)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "601b240b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 训练网络\n",
    "losses = []\n",
    "\n",
    "for i in range(1000):\n",
    "    batch_loss = []\n",
    "    #MINI-Batch方法来进行训练\n",
    "    for start in range(0, len(input_features), batch_size):\n",
    "        end = start + batch_size if start + batch_size < len(input_features) else len(input_features)\n",
    "        xx = torch.tensor(input_features[start: end], dtype=torch.float, requires_grad=True)\n",
    "        yy = torch.tensor(labels[start: end], dtype=torch.float, requires_grad=True)\n",
    "        prediction = my_nn(xx)\n",
    "        loss = cost(prediction, yy)\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward(retain_graph=True)\n",
    "        # 更新操作\n",
    "        optimizer.step()\n",
    "        batch_loss.append(loss.data.numpy())\n",
    "        \n",
    "    # 打印损失\n",
    "    if i % 100 == 0:\n",
    "        losses.append(np.mean(batch_loss))\n",
    "        print(i, np.mean(batch_loss))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "015b9bc8",
   "metadata": {},
   "source": [
    "- 预测训练结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0b36054c",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = torch.tensor(input_features, dtype=torch.float)\n",
    "# 转换numpy格式方便画图\n",
    "predict = my_nn(x).data.numpy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4235291",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 转换日期格式\n",
    "dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]\n",
    "dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]\n",
    "\n",
    "# 创建一个表格来保存日期和其对应的标签数值\n",
    "ture_data = pd.DataFrame(data={'date': dates, 'actual': labels})\n",
    "\n",
    "# 同理，再创建一个来保存日期和其对应的模型预测值\n",
    "months = features[:, feature_list.index('month')]\n",
    "days = features[:, feature_list.index('day')]\n",
    "years = features[:, feature_list.index('year')]\n",
    "\n",
    "test_dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]\n",
    "\n",
    "test_dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in test_dates]\n",
    "\n",
    "predictions_data = pd.DataFrame(data={'date': test_dates, 'prediction': predict.reshape(-1)})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "87ec61a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 画图\n",
    "\n",
    "# 真实值\n",
    "plt.plot(true_data['date'], true_data['actual'], 'b-', label='actual')\n",
    "\n",
    "# 预测值\n",
    "plt.plot(predictions_data['date'], predictions_data['prediction'], 'ro', label='prediction')\n",
    "plt.xticks(rotation='60')\n",
    "plt.legend()\n",
    "\n",
    "# 图名\n",
    "plt.xlabel('Date'); plt.ylabel('Maximum Temperature (F)'); plt.title('Actual and Predicted Values');"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
